package org.btik.bean.impl;

import org.btik.bean.impl.temporary.ConstructorArg;
import org.btik.bean.impl.temporary.UnConstructedBean;
import org.btik.context.exception.LightContextException;
import org.btik.light.tool.type.CollectionUtil;
import org.btik.light.tool.type.StringUtil;
import org.btik.light.tool.type.TypeUtil;

import java.lang.reflect.*;
import java.util.List;

public class BeanCreator {
    static ClassLoader classLoader;

    static Object newInstance(String beanClass, String beanId, List<ConstructorArg> constructorArgs) {
        try {
            // 尝试加载类
            Class<?> aClass = classLoader.loadClass(beanClass);
            if (CollectionUtil.isNotEmpty(constructorArgs)) {
                return newInstanceWithArg(aClass, beanId, constructorArgs);
            }
            Constructor<?> constructor = aClass.getConstructor();
            return constructor.newInstance();
        } catch (Exception e) {
            throw new LightContextException(e);
        }
    }

    /**
     * @param type   类型
     * @param beanId bean唯一标识
     * @param args   构造方法的参数，不能为空
     */
    static Object newInstanceWithArg(Class<?> type, String beanId, List<ConstructorArg> args) throws InvocationTargetException, InstantiationException, IllegalAccessException {

        Constructor<?>[] constructors = type.getConstructors();
        int matchProgress;
        int valueSetProgress;
        findConstructor:
        for (Constructor<?> constructor : constructors) {
            Parameter[] parameters = constructor.getParameters();
            int length = parameters.length;
            if (length != args.size()) {
                continue;
            }
            matchProgress = 0;
            valueSetProgress = 0;
            Object[] argValues = new Object[length];
            for (int i = 0; i < length; i++) {
                Parameter parameter = parameters[i];
                String name = parameter.getName();
                Class<?> argType = parameter.getType();
                ConstructorArg constructorArg = args.get(i);
                constructorArg.setIndex(i);
                if (!name.equals(constructorArg.getName()) && !argType.getName().equals(constructorArg.getType())) {
                    continue findConstructor;
                }
                matchProgress++;
                if (constructorArg.getValue() != null) {
                    trySetValue(constructorArg, argType);
                    argValues[i] = constructorArg.getParsedValue();
                    valueSetProgress++;
                }


            }

            // 当所有的值都设置过了，执行构造方法创建对象
            if (valueSetProgress == length) {
                return constructor.newInstance(argValues);
            }
            // 不是全部是值，但构造器已经匹配完成
            if (matchProgress == length) {
                UnConstructedBean unConstructedBean = new UnConstructedBean();
                unConstructedBean.setConstructor(constructor);
                unConstructedBean.setArgs(args);
                unConstructedBean.setBeanId(beanId);
                unConstructedBean.setType(type);
                unConstructedBean.setConstructorArg(argValues);
                return unConstructedBean;
            }
        }
        StringBuilder argStr = new StringBuilder();
        for (ConstructorArg arg : args) {
            argStr.append(arg.getType()).append(',');
        }
        throw new LightContextException("No constructor was found for these parameters "
                + argStr.deleteCharAt(argStr.length() - 1));
    }

    private static void trySetValue(ConstructorArg constructorArg, Class<?> type) {
        String valueStr = constructorArg.getValue();

        Object o;
        try {
            o = TypeUtil.parseValueFromString(valueStr, type);
        } catch (Exception e) {
            throw new LightContextException(e);
        }
        constructorArg.setParsedValue(o);
        constructorArg.setTypeClass(type);

    }


    public static void setCommonTypeValue(Object object, String fieldName, String value) {
        try {
            Class<?> aClass = object.getClass();
            Method setMethod = findSetter(aClass, fieldName);
            // 无public set方法则直接对属性进行赋值
            if (setMethod == null) {
                Field declaredField = aClass.getDeclaredField(fieldName);
                Class<?> type = declaredField.getType();
                Object parsedValue = TypeUtil.parseValueFromString(value, type);
                declaredField.setAccessible(true);
                declaredField.set(object, parsedValue);
                return;
            }

            Parameter[] parameters = setMethod.getParameters();
            Parameter parameter = parameters[0];
            Class<?> type = parameter.getType();
            Object parsedValue = TypeUtil.parseValueFromString(value, type);
            setMethod.invoke(object, parsedValue);
        } catch (Exception e) {
            throw new LightContextException(e);
        }
    }

    public static void setValue(Object object, String fieldName, Object value) {
        try {
            Class<?> aClass = object.getClass();
            Method setMethod = findSetter(aClass, fieldName);
            if (setMethod != null) {
                setMethod.invoke(object, value);
                return;
            }
            // 无public set方法则直接对属性进行赋值
            Field declaredField = aClass.getDeclaredField(fieldName);
            declaredField.setAccessible(true);
            declaredField.set(object, value);
        } catch (Exception e) {
            throw new LightContextException(e);
        }
    }

    public static Method findSetter(Class<?> aClass, String fieldName) {
        Method[] methods = aClass.getMethods();
        Method setMethod = null;
        String setMethodName = "set" + StringUtil.toFirstCharUp(fieldName);
        for (Method method : methods) {
            String methodName = method.getName();
            if (Modifier.isPublic(method.getModifiers()) &&
                    method.getParameters().length == 1 && methodName.equals(setMethodName)) {
                setMethod = method;
                break;
            }
        }
        return setMethod;
    }
}
